package cn.zf233.jwtshiro.config.shiro;

import cn.zf233.jwtshiro.config.shiro.open.OpenRealm;
import cn.zf233.jwtshiro.config.shiro.sms.VCodeRealm;
import cn.zf233.jwtshiro.config.shiro.upwd.UPWDRealm;
import cn.zf233.jwtshiro.service.UserService;
import cn.zf233.jwtshiro.util.RedisUtil;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.servlet.Filter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by zf233 on 2021/7/12
 */
@Configuration
public class ShiroConfig {

    @Bean
    public JwtRealm jwtRealm(@Autowired UserService userService,
                             @Autowired RedisUtil redisUtil) {
        return new JwtRealm(userService, redisUtil);
    }

    @Bean
    public UPWDRealm uPWDRealm(@Autowired UserService userService) {
        return new UPWDRealm(userService);
    }

    @Bean
    public OpenRealm openRealm(@Autowired UserService userService) {
        return new OpenRealm(userService);
    }

    @Bean
    public VCodeRealm vCodeRealm(@Autowired UserService userService) {
        return new VCodeRealm(userService);
    }

    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(JwtRealm jwtRealm, UPWDRealm uPWDRealm, OpenRealm openRealm, VCodeRealm vCodeRealm, CacheManager myRedisCacheManager) {
        ModularRealmAuthenticator authenticator = new MyModularRealmAuthenticator();

        // 设置认证策略，有一个认证成功即可（其余跳过）
        authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());

        // 扩展父类原方法，捕获原始异常
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setAuthenticator(authenticator);

        // 使用自己的realm
        // securityManager.setRealm(jwtRealm);
        securityManager.setRealms(Arrays.asList(jwtRealm, uPWDRealm, openRealm, vCodeRealm));

        // 关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        // 设置缓存管理
        securityManager.setCacheManager(myRedisCacheManager);

        return securityManager;
    }

    // 配置过滤器工厂, 管理和配置过滤器匹配规则
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean factoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        // 添加自己的过滤器
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwtFilter", new JwtFilter());
        factoryBean.setFilters(filterMap);

        // 配置管理器
        factoryBean.setSecurityManager(securityManager);

        // 这里设置了不起作用，折中使用全局异常处理捕获进行无权限访问的后续处理
        // 只有perms，roles，ssl，rest，port是属于AuthorizationFilter，才会调用以下所设置的接口
        // 而anon，authcBasic，auchc，user是AuthenticationFilter，所以unauthorizedUrl设置后页面不跳转
        // 我们自定义的jwtFilter是AuthenticationFilter
        // factoryBean.setUnauthorizedUrl();

        //配置基本的策略规则,那些可以直接访问  那些需要权限  按照顺序,先配置的起作用
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        filterRuleMap.put("/login/**", "anon");
        filterRuleMap.put("/test/hello", "anon");
        filterRuleMap.put("/test/**", "jwtFilter");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    /**
     * 下面的代码是添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib，防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public StringRedisTemplate redisTemplate(RedisConnectionFactory factory) {

        StringRedisTemplate redisTemplate = new StringRedisTemplate(factory);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
        // GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);              //键值序列化方式
        redisTemplate.setValueSerializer(jdkSerializationRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);          //绑定hash的序列化方式
        redisTemplate.setHashValueSerializer(jdkSerializationRedisSerializer);
        // redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }

    /**
     *  自定义封装的redis缓存操作类
     * */
    @Bean("myRedisCacheManager")
    public CacheManager myRedisCacheManager(StringRedisTemplate redisTemplate){
        return new MyRedisCacheManager(redisTemplate);
    }
}